package cn.topcodes.tcsf.admin.web;

import cn.topcode.unicorn.utils.MathUtil;
import cn.topcode.unicorn.utils.RenderUtil;
import cn.topcode.unicorn.utils.StringUtil;
import cn.topcodes.tcsf.admin.commons.api.domain.ApiResponseEntity;
import cn.topcodes.tcsf.admin.commons.generic.event.EntitySavedEvent;
import cn.topcodes.tcsf.admin.domain.entity.RatelimiterRule;
import cn.topcodes.tcsf.admin.service.RatelimiterRuleService;
import com.google.common.util.concurrent.RateLimiter;
import org.apache.http.protocol.UriPatternMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.http.MediaType;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author Unicorn
 */
public class RateLimiterFilter implements Filter, ApplicationListener<EntitySavedEvent> {

    private Logger logger = LoggerFactory.getLogger(RateLimiterFilter.class);

    private UriPatternMatcher uriPatternMatcher = new UriPatternMatcher();

    @Autowired
    private RatelimiterRuleService ratelimiterRuleService;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        List<RatelimiterRule> rules = this.ratelimiterRuleService.findAll();
        for(RatelimiterRule rule : rules) {
            RatelimiterInfo info = buildRatelimiterInfo(rule);
            uriPatternMatcher.register(rule.getUri(), info);
        }
    }


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        RatelimiterInfo info = (RatelimiterInfo) uriPatternMatcher.lookup(req.getRequestURI().replace(req.getContextPath(), ""));
        if(info != null) {
            RateLimiter rateLimiter = info.getRateLimiter();
            RatelimiterRule rule = info.getRule();
            if(rule.getIsTry()) {
                if(rule.getTimeout() != null && StringUtil.isNotBlank(rule.getTimeoutUnit())) {
                    TimeUnit timeUnit = TimeUnit.valueOf(rule.getTimeoutUnit());
                    boolean m = rateLimiter.tryAcquire(rule.getAcquirePermits(), rule.getTimeout(), timeUnit);
                    if(!m) {
                        processBusy(req, resp);
                        return;
                    }
                }else {
                    boolean m = rateLimiter.tryAcquire(rule.getAcquirePermits());
                    if(!m) {
                        processBusy(req, resp);
                        return;
                    }
                }
            }else {
                double m = rateLimiter.acquire(rule.getAcquirePermits());
                if(MathUtil.compare(m, 1.0) > 0) {
                    logger.warn("接口繁忙，已启用限流！接口地址：" + req.getRequestURI() + "，等待耗时：" + m + "秒");
                }
            }

        }
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }

    private void processBusy(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        logger.warn("接口繁忙，已启用限流！接口地址：" + request.getRequestURI());

        String contentType = request.getHeader("accept");
        if(StringUtil.isBlank(contentType)|| contentType.contains(MediaType.TEXT_HTML_VALUE)) {
            request.getRequestDispatcher("/WEB-INF/views/error/busy.jsp").forward(request, response);
        }else {
            ApiResponseEntity apiResponseEntity = ApiResponseEntity.buildFailed("系统繁忙，请稍后重试！");
            RenderUtil.renderJson(apiResponseEntity, response);
        }
    }

    public RatelimiterInfo buildRatelimiterInfo(RatelimiterRule rule) {
        RateLimiter rateLimiter = null;
        if(rule.getWarmupPeriod() != null && StringUtil.isNotBlank(rule.getWarmupTimeUnit())) {
            TimeUnit timeUnit = TimeUnit.valueOf(rule.getWarmupTimeUnit());
            rateLimiter = RateLimiter.create(rule.getPermitsPerSecond(), rule.getWarmupPeriod(), timeUnit);
        }else {
            rateLimiter = RateLimiter.create(rule.getPermitsPerSecond());
        }
        RatelimiterInfo info = new RatelimiterInfo();
        info.setRateLimiter(rateLimiter);
        info.setRule(rule);
        return info;
    }

    @Override
    public void onApplicationEvent(EntitySavedEvent event) {
        if(event.getNewEntity() instanceof RatelimiterRule) {
            RatelimiterRule rule = (RatelimiterRule) event.getNewEntity();
            RatelimiterInfo info = (RatelimiterInfo) uriPatternMatcher.lookup(rule.getUri());
            if(info == null) {
                info = buildRatelimiterInfo(rule);
                uriPatternMatcher.register(rule.getUri(), info);
            }else {
                info.setRule(rule);
                info.getRateLimiter().setRate(rule.getPermitsPerSecond());
            }
        }
    }

    public UriPatternMatcher getUriPatternMatcher() {
        return uriPatternMatcher;
    }
}
